﻿using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Media;
using DevExpress.Mvvm;
using DevExpress.Mvvm.DataAnnotations;
using Framework.Serialization;
using HelixToolkit.Wpf.SharpDX;
using Microsoft.Win32;
using MoreLinq;
using STORAGE;
using static System.Math;
using Camera = HelixToolkit.Wpf.SharpDX.Camera;
using Color = System.Windows.Media.Color;
using OrthographicCamera = HelixToolkit.Wpf.SharpDX.OrthographicCamera;
using Point3D = System.Windows.Media.Media3D.Point3D;
using Vector3 = SharpDX.Vector3;
using Vector2 = SharpDX.Vector2;
using Vector3D = System.Windows.Media.Media3D.Vector3D;

namespace FemExplorer
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    public class MainViewModel : ViewModelBase
    {
        public EffectsManager EffectsManager { get; private set; } = new DefaultEffectsManager();
        public Camera Camera { get; private set; }

        public Vector3D DirectionalLightDirection { get; private set; } = new Vector3D(-2, -5, -2);
        public Color DirectionalLightColor { get; private set; } = Colors.White;
        public Color AmbientLightColor { get; private set; } = Colors.DimGray;

        public Vector3D UpDirection { set; get; } = new Vector3D(0, 0, 1);

        public LineGeometry3D BeamElemGeometry
        {
            get => GetValue<LineGeometry3D>();
            set => SetValue(value);
        }

        public PointGeometry3D NodeGeometry
        {
            get => GetValue<PointGeometry3D>();
            set => SetValue(value);
        }

        public bool IsNodeSymbolVisible
        {
            get => GetValue<bool>();
            set => SetValue(value);
        }

        public LineGeometry3D SectionGeometry1
        {
            get => GetValue<LineGeometry3D>();
            set => SetValue(value);
        }

        public LineGeometry3D SectionGeometry2
        {
            get => GetValue<LineGeometry3D>();
            set => SetValue(value);
        }

        public MainViewModel()
        {
            Camera = new OrthographicCamera
            {
                Position = new Point3D(1000, 1000, 1000),
                LookDirection = new Vector3D(-1000, -1000, -1000),
                UpDirection = new Vector3D(0, 0, 1),
                FarPlaneDistance = 100000
            };

            IsNodeSymbolVisible = false;
        }

        [Command]
        public void LoadModel()
        {
            var ofd = new OpenFileDialog
            {
                DefaultExt = ".3db",
                Filter = "3D Bridge database (.3db)|*.3db",
            };
            if (ofd.ShowDialog() != true)
                return;

            var filePath = ofd.FileName;
            Debug.Assert(File.Exists(filePath));

            //
            var formatter = new BinaryFormatter();
            using (var fs = new FileStream(filePath, FileMode.Open))
            {
                var data = (byte[]) formatter.Deserialize(fs);
                using (var reader = new SerializationReader(data))
                {
                    reader.ReadOwnedData(StoreData, new object());
                }

                data = null;
            }

            //
            NodeDict = StoreData.Vertexes.Cast<Vertex>()
                .Select(vertex => new Node
                {
                    Id = vertex.SN,
                    X = vertex.X,
                    Y = vertex.Y,
                    Z = vertex.Z,
                }).ToDictionary(node => node.Id, node => node);

            ElemDict = StoreData.Beams.Cast<Beam>()
                .Select(beam => new Elem
                {
                    Id = beam.SN,
                    NodeIds = new[] {beam.ISN, beam.JSN}
                }).ToDictionary(elem => elem.Id, elem => elem);

            //
            // Elems
            //
            var builder = new LineBuilder();
            ElemDict.ForEach(kv =>
            {
                var elem = kv.Value;
                var n1 = elem.NodeIds[0];
                var n2 = elem.NodeIds[1];
                var node1 = NodeDict[n1];
                var node2 = NodeDict[n2];
                builder.AddLine(node1.ToVector3(), node2.ToVector3());
            });
            BeamElemGeometry = builder.ToLineGeometry3D();

            //
            // Nodes
            //
            var vectors = new Vector3Collection(ElemDict.SelectMany(kv => kv.Value.NodeIds)
                .Distinct()
                .Select(nodeId => NodeDict[nodeId].ToVector3()));
            NodeGeometry = new PointGeometry3D
            {
                Positions = vectors,
                Indices = new IntCollection(Enumerable.Range(0, vectors.Count))
            };

            //
            // Sections
            //
            LineGeometry3D CreateLineGeometryFromLoop(ArrayList loop)
            {
                Debug.Assert(loop != null);
                var loopNodes = loop.Cast<SimplePoint>().Select(pt => new Node
                {
                    Id = (uint) pt.SN,
                    X = pt.X,
                    Y = pt.Y,
                }).ToList();
                loopNodes.Add(loopNodes.First());

                builder = new LineBuilder();
                for (int i = 0; i < loopNodes.Count - 1; i++)
                {
                    var n1 = loopNodes[i];
                    var n2 = loopNodes[i + 1];
                    builder.AddLine(n1.ToVector3(), n2.ToVector3());
                }

                return builder.ToLineGeometry3D();
            }

            SectionGeometry1 = CreateLineGeometryFromLoop(StoreData.Sections[0].SecLoops[0] as ArrayList);
            SectionGeometry2 = CreateLineGeometryFromLoop(StoreData.Sections[0].SecLoops[1] as ArrayList);
        }

        private StorageGroup StoreData { get; set; } = new StorageGroup();
        private Dictionary<uint, Node> NodeDict { get; set; } = new Dictionary<uint, Node>();
        private Dictionary<uint, Elem> ElemDict { get; set; } = new Dictionary<uint, Elem>();
    }

    public class Node
    {
        public uint Id { get; set; }
        public double X { get; set; }
        public double Y { get; set; }
        public double Z { get; set; } = 0;

        public double DistTo(Node other)
            => Sqrt(Pow(X - other.X, 2) + Pow(Y - other.Y, 2) + Pow(Z - other.Z, 2));

        public Vector3 ToVector3() => new Vector3((float) X, (float) Y, (float) Z);
    }

    public class Elem
    {
        public uint Id { get; set; }
        public IList<uint> NodeIds { get; set; }
    }

    public class Section
    {
        public uint Id { get; set; }
    }
}